package com.os.specs;

import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.SingularAttribute;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import static com.google.common.collect.Iterables.toArray;

/**
 * 结合Specification和自定义Repository实现类定制一个自动模糊查询。
 * 对任意的实体对象进行查询，对象里有几个值我们就查询几个，
 * 当值为字符型时我们就自动like查询，其余类型使用自动等于查询，没有值我们就查询全部
 */
public class CustomerSpecs {

    public static<T> Specification<T> byAuto(final EntityManager entityManager, final T example){
        final Class<T> type = (Class<T>)example.getClass();  //获取当前实体类对象类的类型

        return new Specification<T>() {
            @Override
            public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicateList = new ArrayList<>();
                EntityType<T> entityType = entityManager.getMetamodel().entity(type);

                for(Attribute<T, ?> attr : entityType.getDeclaredAttributes()){
                    Object attrValue = getValue(example, attr);
                    if(attrValue != null){
                        if(attr.getJavaType() == String.class){
                            if(!StringUtils.isEmpty(attrValue)){
                                predicateList.add(criteriaBuilder.like(root.get(attribute(entityType, attr.getName(), String.class))
                                        , pattern((String) attrValue)));
                            } else {
                                predicateList.add(criteriaBuilder.equal(root.get(attribute(entityType, attr.getName(),
                                        String.class)),
                                        pattern((String) attrValue)));
                            }
                        }
                    }
                }

                return predicateList.isEmpty() ? criteriaBuilder.conjunction()
                        : criteriaBuilder.and(toArray(predicateList, Predicate.class));
            }

            private <T> Object getValue(T example, Attribute<T, ?> attr){
                return ReflectionUtils.getField((Field) attr.getJavaMember(), example);
            }

            private <E, T> SingularAttribute<T, E> attribute(EntityType<T> entityType
                    , String fieldName
                    , Class<E> fieldClass){
                return entityType.getDeclaredSingularAttribute(fieldName, fieldClass);
            }

        };
    }

    static  private  String pattern(String str){
        return "%" + str + "%";
    }

}
